package util.wx;

import org.apache.commons.codec.digest.DigestUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import util.http.HttpUtil;
import util.xml.XmlUtil;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 微信 工具类
 * @Author zt
 * @date 2021/11/17 10:28
 * @Version 1.0
 */
public class WXUtil {
    /**
     * XML格式字符串转换为Map
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * 将Map转换为XML格式的字符串
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }


    /**
     * 生成 MD5
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 获取当前时间戳，单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳，单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /**
     * 生成 uuid， 即用来标识一笔单，也用做 nonce_str
     * @return
     */
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    static String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 生成h5支付连接
     * @param appId appid
     * @param appKey 秘钥
     * @param merId 商户号
     * @param orderId 订单号
     * @param amount 金额
     * @param notifyUrl url地址
     * @param ip ip地址
     * @return 结果
     * @param openId  openId
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static Map getWeixinPayMap(String appId, String appKey, String merId, String orderId, String amount, String notifyUrl, String ip, String openId, String timeStamp)
            throws Exception {
        // String appsecret = PayConfigUtil.APP_SECRET; // appsecret
        String currTime = getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = buildRandom(4) + "";
        String nonce_str = strTime + strRandom;
        // 价格 注意：价格的单位是分
        amount = String.valueOf((long) (Double.parseDouble(amount) * 100));
        String body = "商品名称"; // 商品名称
        String trade_type = "JSAPI";
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        packageParams.put("appid", appId);
        packageParams.put("mch_id", merId);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", body);
        packageParams.put("device_info", "WEB");
        if (openId != null) {
            packageParams.put("openid", openId);
        }
        packageParams.put("product_id", orderId);
        // 订单号
        packageParams.put("out_trade_no", orderId);
        packageParams.put("total_fee", amount);
        // 获取发起电脑 ip
        packageParams.put("spbill_create_ip", ip);
        // 回调接口
        packageParams.put("notify_url", notifyUrl);
        packageParams.put("trade_type", trade_type);
        String sign = createSign("UTF-8", packageParams, appKey);
        packageParams.put("sign", sign);
        String requestXML = getRequestXml(packageParams);
        System.out.println("请求：" + requestXML);
        String resXml = HttpUtil.doPost(UFDODER_URL, requestXML);
        System.out.println("响应：" + resXml);
        Map map = (Map) XmlUtil.converyToJavaBean(resXml,Map.class);

        SortedMap<Object, Object> packagePayParams = new TreeMap<Object, Object>();
        packagePayParams.put("appId", appId);
        packagePayParams.put("timeStamp", timeStamp);
        packagePayParams.put("nonceStr", (String) map.get("nonce_str"));
        packagePayParams.put("package", "prepay_id=" + (String) map.get("prepay_id"));
        packagePayParams.put("signType", "MD5");
        // 再次签名
        String paySign = createSign("UTF-8", packagePayParams, appKey);
        map.put("paySign", paySign);
        //map.put("nonceStr", (String) map.get("nonce_str"));
        return map;
    }

    /**
     * 生成支付二维码
     * @param appId appid
     * @param appKey 秘钥
     * @param merId 商户号
     * @param orderId 订单号
     * @param amount 金额（元）
     * @param notifyUrl url地址
     * @param ip ip地址
     * @return 结果
     */
    public static String createQrcode(String appId, String appKey, String merId, String orderId, String amount, String notifyUrl, String ip) throws Exception {
        // String appsecret = PayConfigUtil.APP_SECRET; // appsecret
        String currTime = getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = buildRandom(4) + "";
        String nonce_str = strTime + strRandom;
        // 价格 注意：价格的单位是分
        amount = String.valueOf((long) (Double.parseDouble(amount) * 100));
        String body = "扫描付款"; // 商品名称
        String trade_type = "NATIVE";

        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        packageParams.put("appid", appId);
        packageParams.put("mch_id", merId);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", body);
        packageParams.put("device_info", "web");
        packageParams.put("product_id", orderId);
        // 订单号
        packageParams.put("out_trade_no", orderId);
        packageParams.put("total_fee", amount);
        // 获取发起电脑 ip
        packageParams.put("spbill_create_ip", ip);
        // 回调接口
        packageParams.put("notify_url", notifyUrl);
        packageParams.put("trade_type", trade_type);

        String sign = createSign("UTF-8", packageParams, appKey);
        packageParams.put("sign", sign);

        String requestXML = getRequestXml(packageParams);
        System.out.println("请求：" + requestXML);

        String resXml = HttpUtil.doPost(UFDODER_URL, requestXML);
        System.out.println("响应：" + resXml);
        @SuppressWarnings("rawtypes")
        Map map = (Map) XmlUtil.converyToJavaBean(resXml,Map.class);
        @SuppressWarnings("unused")
        String return_code = (String) map.get("return_code");
        String prepay_id = (String) map.get("prepay_id");
        @SuppressWarnings("unused")
        String urlCode = (String) map.get("code_url");
        sign = (String) map.get("sign");
        return urlCode;

    }

    /**
     * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     * @return boolean
     * @throws Exception  异常
     */
    @SuppressWarnings("rawtypes")
    public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) throws Exception {
        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k) && null != v && !"".equals(v)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + API_KEY);

        // 算出摘要
        String mysign = MD5(sb.toString()).toLowerCase();
        String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
        // System.out.println(tenpaySign + "    " + mysign);
        return tenpaySign.equals(mysign);
    }




    /**
     * @param map
     * @return 结果
     */
    private static String getSign(Map<String, String> map) {
        StringBuffer sb = new StringBuffer();
        @SuppressWarnings("rawtypes")
        Set es = map.entrySet();// 所有参与传参的参数按照accsii排序（升序）
        @SuppressWarnings("rawtypes")
        Iterator it = es.iterator();
        while (it.hasNext()) {
            @SuppressWarnings("rawtypes")
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        // 你在公众号内设置的密钥
        sb.append("key=" + "e30056f6d0d1a14855e92902a012d778");
        // MD5加密方法，文章后面会提供工具类下载
        String sign = DigestUtils.md5Hex(getContentBytes(sb.toString(), "UTF-8")).toUpperCase();
        return sign;
    }

    /**
     * sign签名
     * @param characterEncoding 编码格式
     * @param packageParams 请求参数
     * @return 结果
     */
    @SuppressWarnings("rawtypes")
    public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + API_KEY);
        // String sign = MD5Util.MD5Encode(sb.toString(),
        // characterEncoding).toUpperCase();
        String sign = DigestUtils.md5Hex(getContentBytes(sb.toString(), characterEncoding)).toUpperCase();
        return sign;
    }

    private static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }

    /**
     * @param parameters 请求参数
     * @return 结果
     */
    @SuppressWarnings("rawtypes")
    public static String getRequestXml(SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            // if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) ||
            // "sign".equalsIgnoreCase(k)) {
            if ("attach".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
            } else {
                sb.append("<" + k + ">" + v + "</" + k + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 取出一个指定长度大小的随机正整数.
     * @param length int 设定所取出随机数的长度。length小于11
     * @return int 返回生成的随机数。
     */
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    /**
     * 获取当前时间 yyyyMMddHHmmss
     * @return String
     */
    public static String getCurrTime() {
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = outFormat.format(now);
        return s;
    }

    /**
     * 获取时间戳
     * @return 结果
     */
    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }


}
